#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations

import json
import re

from typing import Any

from autoapi.common.log import log
from autoapi.enums.setup_type import SetupType
from autoapi.enums.teardown_type import TeardownType
from autoapi.core import hooks


# 导入hooks模块
try:
    from autoapi.core.hooks import *
except ImportError as e:
    log.error(f"无法导入autoapi.core.hooks模块: {e}")
    raise


class HookExecutor:
    def __init__(self) -> None:
        # hook 开头: a-zA-Z_
        # hook 表达: ${func()} 或 ${func(1, 2)} 或 ${func("string")}
        self.func_re = re.compile(r"\${([a-zA-Z_]\w*\([\s\S]*?\))}")

    def hook_func_value_replace(self, target: dict) -> Any:
        """
        执行除前后置 hook 以外的所有 hook 函数并替换为它的返回值。
        setup/teardown 中的 hook 表达式不参与替换，仅执行。

        :param target: 用例原始数据字典
        :return: hook 执行和替换后的数据字典
        """
        if not target["test_steps"].get("is_run", True):
            return target

        # ----- 1. 提取 setup/teardown 中的 hook 函数并暂时移除，防止被 eval 替换 -----
        setup = target["test_steps"].get("setup", [])
        teardown = target["test_steps"].get("teardown", [])

        setup_hooks = [
            (i, item[SetupType.HOOK])
            for i, item in enumerate(setup)
            if SetupType.HOOK in item
        ]
        teardown_hooks = [
            (i, item[TeardownType.HOOK])
            for i, item in enumerate(teardown)
            if TeardownType.HOOK in item
        ]

        # 移除含 hook 的项
        target["test_steps"]["setup"] = [
            item for item in setup if SetupType.HOOK not in item
        ]
        target["test_steps"]["teardown"] = [
            item for item in teardown if TeardownType.HOOK not in item
        ]

        # ----- 2. 替换其他字段中的 hook 表达式（只针对非 setup/teardown） -----
        str_target = json.dumps(target, ensure_ascii=False)

        while True:
            match = self.func_re.search(str_target)
            if not match:
                break

            hook_key = match.group(1)  # 如 get_phone()
            func_name = hook_key.split("(")[0]

            try:
                if func_name not in hooks.__registered_hooks__:
                    raise ValueError(f"未注册的 hook 函数: {func_name}")
                value = str(eval(hook_key, hooks.__registered_hooks__))
                str_target = self.func_re.sub(value, str_target, count=1)
                log.info(f"字段 hook 函数 {hook_key} 返回值替换完成")
            except Exception as e:
                log.error(f"字段 hook 函数 {hook_key} 返回值替换失败: {e}")
                raise e

        dict_target = json.loads(str_target)

        # ----- 3. 恢复 setup/teardown 并执行对应 hook 函数 -----
        for i, func in setup_hooks:
            hook_expr = f"{func}"
            dict_target["test_steps"].setdefault("setup", []).insert(
                i, {SetupType.HOOK: hook_expr}
            )

        for i, func in teardown_hooks:
            hook_expr = f"{func}"
            dict_target["test_steps"].setdefault("teardown", []).insert(
                i, {TeardownType.HOOK: hook_expr}
            )

        return dict_target

    def exec_hook_func(self, hook_var: str) -> None:
        """
        执行 hook 函数不返回任何值，仅限 setup/teardown 中 hook 使用

        :param hook_var: e.g. ${init_db_contract()}
        """
        if not isinstance(hook_var, str):
            return

        key = self.func_re.search(hook_var)
        if not key:
            log.error(f"无效的hook表达式: {hook_var}")
            return

        try:
            func = key.group(1)
            func_name = func.split("(")[0]
            if func_name not in hooks.__registered_hooks__:
                log.error(f"未注册的 hook 函数: {func_name}")
                return
            log.info(f"执行 hook：{func}")
            exec(func, hooks.__registered_hooks__)
        except Exception as e:
            log.error(f"执行hook失败: {e}")

    @staticmethod
    def exec_any_code(code: str) -> bool:
        """
        执行任何函数

        :param code:
        :return:
        """
        exec("import os")
        exec("import sys")
        result = eval(code)
        log.info(f"执行代码：{code}")
        return result


hook_executor = HookExecutor()
